// *************************************************************************************************
//
// MK3D Engine
// --------------------------------------
// Copyright (C) 2007-2011 Zeb (mailto: zebbey@gmail.com)
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// *************************************************************************************************

#include "mk_header.h"
#include "mk_material.h"
#include "mk_error.h"
#include "mk_engine.h"
#include "mk_resource_manager.h"
#include "mk_memory_block.h"
#include "mk_file_system.h"
#include "mk_shader.h"
#include "mk_material_property.h"
#include "mk_mp_alpha_blend.h"
#include "mk_mp_alpha_test.h"
#include "mk_mp_culling_mode.h"
#include "mk_mp_depth_state.h"
#include "mk_mp_ambient.h"
#include "mk_mp_diffuse.h"
#include "mk_mp_specular.h"
#include "mk_mp_fog.h"

namespace MK3DE
{
	CMPFog* CMaterial::_s_fogProperty = NULL;

	BOOL CMaterial::Create()
	{
		BOOL ret = FALSE;
		_s_fogProperty = new CMPFog();
		MK_PROCESS_ERROR(_s_fogProperty);

		ret = TRUE;
Exit0:
		return TRUE;
	}

	void CMaterial::Destroy()
	{
		SAFE_DELETE(_s_fogProperty);
	}

	CMPFog* CMaterial::GetFogProperty()
	{
		return _s_fogProperty;
	}

	CMaterial::CMaterial()
	{
		memset(_renderStateProperties, 0, sizeof(_renderStateProperties));
		memset(_shaderConstantProperties, 0, sizeof(_shaderConstantProperties));
	}

	CMaterial::~CMaterial()
	{
		__Clean();
	}

	BOOL CMaterial::LoadFromFile(const char* name)
	{
		BOOL ret = FALSE;
		int result = 1;
		TiXmlDocument* pXML = NULL;
		TiXmlElement* node = NULL;
		TiXmlElement* root = NULL;
		CMemoryBlock block;
		CResourceManager* resourceManager = GetEngine()->GetResourceManager();

		__CleanTextureMapping();

		ret = GetEngine()->GetFileSystem()->ReadFile(name, &block);
		MK_PROCESS_ERROR(ret);

		pXML = new TiXmlDocument();
		MK_PROCESS_ERROR(pXML);

		pXML->SetCondenseWhiteSpace(false);
		pXML->Parse((const char*)block.GetBuffer());
		MK_PROCESS_ERROR(pXML->Error() == false);

		root = pXML->RootElement();
		MK_PROCESS_ERROR(root);

		result = strcmp(root->Value(), "Material");
		MK_PROCESS_ERROR(result == 0);

		node = root->FirstChildElement("TextureMapping");
		while (node)
		{
			CTextureMapping::Map textureMap;
			CTexture2D* texture = NULL;
			const char* textureFile = node->Attribute("TextureFile");
			const char* textureName = node->Attribute("TextureName");
			MK_PROCESS_ERROR(textureFile);
			MK_PROCESS_ERROR(textureName);

			ret = resourceManager->GetTexture2DFromFile(textureFile, texture);
			MK_PROCESS_ERROR(ret);

			textureMap.fileName = textureFile;
			textureMap.textureName = textureName;
			textureMap.textureData = (CTextureMapping::TextureData)texture;
			AddMap(textureMap);

			node = node->NextSiblingElement("TextureMapping");
		}

		node = root->FirstChildElement("Ambient");
		if (node)
		{
			CMPAmbient* amp = (CMPAmbient*)_shaderConstantProperties[SCP_AMBIENT];
			int bEnable;
			float r, g, b;
			result = node->QueryIntAttribute("Enable", &bEnable);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("r", &r);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("g", &g);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("b", &b);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			amp->SetAmbient((BOOL)bEnable, D3DXCOLOR(r, g, b, 1.0f));
		}

		node = root->FirstChildElement("Diffuse");
		if (node)
		{
			CMPDiffuse* dmp = (CMPDiffuse*)_shaderConstantProperties[SCP_DIFFUSE];
			int bEnable;
			float r, g, b;
			result = node->QueryIntAttribute("Enable", &bEnable);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("r", &r);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("g", &g);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("b", &b);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			dmp->SetDiffuse((BOOL)bEnable, D3DXCOLOR(r, g, b, 1.0f));
		}

		node = root->FirstChildElement("Specular");
		if (node)
		{
			CMPSpecular* smp = (CMPSpecular*)_shaderConstantProperties[SCP_SPECULAR];
			int bEnable;
			float r, g, b;
			float shininess;
			result = node->QueryIntAttribute("Enable", &bEnable);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("r", &r);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("g", &g);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("b", &b);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryFloatAttribute("Shininess", &shininess);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			smp->SetSpecular((BOOL)bEnable, D3DXCOLOR(r, g, b, 1.0f), shininess);
		}

		node = root->FirstChildElement("AlphaBlend");
		if (node)
		{
			CMPAlphaBlend* abmp = (CMPAlphaBlend*)_renderStateProperties[RSP_ALPHA_BLEND];
			int bEnable, srcBlend, destBlend;
			result = node->QueryIntAttribute("Enable", &bEnable);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryIntAttribute("SrcBlend", &srcBlend);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryIntAttribute("DestBlend", &destBlend);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			abmp->SetAlphaBlend((BOOL)bEnable, (BLEND_PARAM)srcBlend, (BLEND_PARAM)destBlend);
		}

		node = root->FirstChildElement("AlphaTest");
		if (node)
		{
			CMPAlphaTest* atmp = (CMPAlphaTest*)_renderStateProperties[RSP_ALPHA_TEST];
			int bEnable, type, ref;
			result = node->QueryIntAttribute("Enable", &bEnable);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryIntAttribute("Type", &type);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryIntAttribute("Ref", &ref);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			atmp->SetAlphaTest((BOOL)bEnable, (ALPHA_TEST_TYPE)type, (DWORD)ref);
		}

		node = root->FirstChildElement("CullingMode");
		if (node)
		{
			CMPCullingMode* cmmp = (CMPCullingMode*)_renderStateProperties[RSP_CULLING_MODE];
			int cullingMode;
			result = node->QueryIntAttribute("Mode", &cullingMode);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			cmmp->SetCullingMode((CULLING_MODE)cullingMode);
		}

		node = root->FirstChildElement("DepthState");
		if (node)
		{
			CMPDepthState* dsmp = (CMPDepthState*)_renderStateProperties[RSP_DEPTH_STATE];
			int bEnable, bWriteEnable;
			result = node->QueryIntAttribute("Enable", &bEnable);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = node->QueryIntAttribute("WriteEnable", &bWriteEnable);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			dsmp->SetDepthState((BOOL)bEnable, (BOOL)bWriteEnable);
		}

		_fileName = name;

		ret = TRUE;
Exit0:
		SAFE_DELETE(pXML);

		return ret;
	}

	CMPAmbient* CMaterial::GetAmbientProperty()
	{
		return (CMPAmbient*)_shaderConstantProperties[SCP_AMBIENT];
	}

	CMPDiffuse* CMaterial::GetDiffuseProperty()
	{
		return (CMPDiffuse*)_shaderConstantProperties[SCP_DIFFUSE];
	}

	CMPSpecular* CMaterial::GetSpecularProperty()
	{
		return (CMPSpecular*)_shaderConstantProperties[SCP_SPECULAR];
	}

	CMPAlphaBlend* CMaterial::GetAlphaBlendProperty()
	{
		return (CMPAlphaBlend*)_renderStateProperties[RSP_ALPHA_BLEND];
	}

	CMPAlphaTest* CMaterial::GetAlphaTestProperty()
	{
		return (CMPAlphaTest*)_renderStateProperties[RSP_ALPHA_TEST];
	}

	CMPCullingMode* CMaterial::GetCullingModeProperty()
	{
		return (CMPCullingMode*)_renderStateProperties[RSP_CULLING_MODE];
	}

	CMPDepthState* CMaterial::GetDepthStateProperty()
	{
		return (CMPDepthState*)_renderStateProperties[RSP_DEPTH_STATE];
	}

	void CMaterial::SetMaterial(CScene* pScene, CShader* pShader, BOOL bShadow)
	{
		if (!pScene || !pShader)
			return;

		// Set texture.
		for (DWORD j = 0; j < (DWORD)_vecMap.size(); ++j)
		{
			CTextureMapping::Map& textureMap = _vecMap[j];
			CTexture2D* tex = (CTexture2D*)textureMap.textureData;
			pShader->SetTexture(textureMap.textureName.c_str(), tex);
		}

		if (!bShadow)
		{
			// Set render state.
			for (int i = 0; i < (int)RSP_MAX; ++i)
				_renderStateProperties[i]->OnSetRenderState();

			// Set shader constants.
			for (int i = 0; i < (int)SCP_MAX; ++i)
				_shaderConstantProperties[i]->OnSetShaderConstant(pShader, pScene);

			// Set global properties.
			_s_fogProperty->OnSetShaderConstant(pShader, pScene);
		}
	}

	std::string CMaterial::GenerateTechniquePostfix(BOOL bShadowRecv)
	{
		_techPostfix.str("");
		_techPostfix.clear();

		//! check all local properties.
		bool haveLight = false;
#define SET_LIGHT_PARAM(s) { if (!haveLight) { _techPostfix << '_'; haveLight = true; } _techPostfix << s; }

		if (_shaderConstantProperties[SCP_AMBIENT]->IsNeedToSet()) SET_LIGHT_PARAM('A');
		if (_shaderConstantProperties[SCP_DIFFUSE]->IsNeedToSet()) SET_LIGHT_PARAM('D');
		if (_shaderConstantProperties[SCP_SPECULAR]->IsNeedToSet()) SET_LIGHT_PARAM('S');

		//! check all global properties.
		if (_s_fogProperty->IsNeedToSet()) _techPostfix << "_FOG";
		if (bShadowRecv) _techPostfix << "_SHADOW";

		return _techPostfix.str();
	}

	void CMaterial::RestoreMaterial()
	{
		for (int i = 0; i < (int)RSP_MAX; ++i)
			_renderStateProperties[i]->OnResetRenderState();
	}

	BOOL CMaterial::CreateAllProperties()
	{
		_shaderConstantProperties[SCP_AMBIENT] = new CMPAmbient();
		MK_PROCESS_ERROR(_shaderConstantProperties[SCP_AMBIENT]);

		_shaderConstantProperties[SCP_DIFFUSE] = new CMPDiffuse();
		MK_PROCESS_ERROR(_shaderConstantProperties[SCP_DIFFUSE]);

		_shaderConstantProperties[SCP_SPECULAR] = new CMPSpecular();
		MK_PROCESS_ERROR(_shaderConstantProperties[SCP_SPECULAR]);

		_renderStateProperties[RSP_ALPHA_BLEND] = new CMPAlphaBlend();
		MK_PROCESS_ERROR(_renderStateProperties[RSP_ALPHA_BLEND]);

		_renderStateProperties[RSP_ALPHA_TEST] = new CMPAlphaTest();
		MK_PROCESS_ERROR(_renderStateProperties[RSP_ALPHA_TEST]);

		_renderStateProperties[RSP_CULLING_MODE] = new CMPCullingMode();
		MK_PROCESS_ERROR(_renderStateProperties[RSP_CULLING_MODE]);

		_renderStateProperties[RSP_DEPTH_STATE] = new CMPDepthState();
		MK_PROCESS_ERROR(_renderStateProperties[RSP_DEPTH_STATE]);

		return TRUE;
Exit0:
		return FALSE;
	}

	void CMaterial::__CleanTextureMapping()
	{
		std::vector<CTextureMapping::Map>::iterator i = _vecMap.begin();
		while (i != _vecMap.end())
		{
			CTextureMapping::Map& map = *i;
			CTexture2D* texture = (CTexture2D*)map.textureData;
			SAFE_RELEASE(texture);

			++i;
		}

		_vecMap.clear();
	}

	void CMaterial::__Clean()
	{
		for (int i = 0; i < (int)SCP_MAX; ++i)
			SAFE_DELETE(_shaderConstantProperties[i]);

		for (int i = 0; i < (int)RSP_MAX; ++i)
			SAFE_DELETE(_renderStateProperties[i]);

		__CleanTextureMapping();
	}
}